﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Schedule.Support.Services;
using Noris.Schedule.Support.Parser;
using Noris.Schedule.Support.Green;
using Noris.Schedule.Support.DataFace;
using System.Drawing;

namespace Noris.Schedule.Support.Expressions
{
    /// <summary>
    /// Třída, jejímž úkolem je vyhodnocení výrazu na konkrétní text.
    /// Vstupem je objekt implementující IConfiguredText a druh požadovaného textu, výstupem je text odpovídající datům objektu a druhu textu.
    /// Případně může být vstupem explicitní deklarace vzorce, pak se vzorec nenačítá z repozitory ale převezme se - používá se při editaci deklarace vzorce.
    /// Vyhodnocení provádí statická metoda Evaluator
    /// </summary>
    public class Evaluator : IDisposable
    {
        #region Konstrukce
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="configuredText"></param>
		/// <param name="evaluationDataSource"></param>
        protected Evaluator(IConfiguredText configuredText, IEvaluationDataSource evaluationDataSource)
        {
            this.ConfiguredText = configuredText;
            this.EvaluationDataSource = evaluationDataSource;
        }
        void IDisposable.Dispose()
        {
            this.ConfiguredText = null;
            this.FormulaSegment = null;
        }
        /// <summary>
        /// Reference na vstupující text
        /// </summary>
        protected IConfiguredText ConfiguredText { get; set; }
        /// <summary>
        /// Reference na datový zdroj, který řeší evaluaci dat.
        /// </summary>
        protected IEvaluationDataSource EvaluationDataSource { get; set; }
        /// <summary>
        /// Parsovaný vzorec, rozdělený do segmentu s vnitřní strukturou, podle kterého se vytváří výstupní text
        /// </summary>
        protected ParserSegment FormulaSegment { get; set; }
        #endregion
        #region Public static metody
        /// <summary>
        /// Vrátí kompletní sadu přeložených textů pro daný objekt (grafický element).
        /// Najde vzorce pro daný element a vyhodnotí všechny patřičné texty.
        /// </summary>
        /// <param name="configuredText">Objekt, který definuje data pro konkrétní text = záznam, skupinu, atd (implementuje IConfiguredText)</param>
        /// <param name="evaluationDataSource">Objekt, který může podporovat získávání dat o proměnných z paměti</param>
        /// <returns></returns>
        public static EvaluatedTexts EvaluateText(IConfiguredText configuredText, IEvaluationDataSource evaluationDataSource)
        {
            using (Evaluator evaluator = new Evaluator(configuredText, evaluationDataSource))
            {
                EvaluatedTexts result = evaluator.EvaluateConfigText();
                return result;
            }
        }
        /// <summary>
        /// Vrátí konkrétní text pro daný objekt a explicitně daný vzorec textu (formula).
        /// Vzorec se tedy nikde nehledá, tato metoda se používá při editaci deklarace vzorce.
        /// Vzorec se rozloží na přímé texty a definice proměnných, 
        /// proměnné se konkretizují za pomoci daného datového zdroje evaluationDataSource,
        /// a výsledný text se vrací.
        /// </summary>
        /// <param name="configuredText">Objekt, který definuje data pro konkrétní text = záznam, skupinu, atd (implementuje IConfiguredText)</param>
        /// <param name="evaluationDataSource">Objekt, který může podporovat získávání dat o proměnných z paměti</param>
        /// <param name="formula">Vzorec textu explicitně dodaný</param>
        /// <returns></returns>
        public static string GetText(IConfiguredText configuredText, IEvaluationDataSource evaluationDataSource, string formula)
        {
            using (Evaluator evaluator = new Evaluator(configuredText, evaluationDataSource))
            {
                string result = evaluator.EvaluateFormula(formula);
                return result;
            }
        }
        #endregion
        #region Protected instanční výkonné metody
        /// <summary>
        /// Vyhodnotí standarní cestou konfigurované texty pro aktuální element,
        /// výsledky naplní do instance třídy EvaluatedTexts
        /// a tuto instanci vrátí.
        /// </summary>
        /// <returns></returns>
        protected EvaluatedTexts EvaluateConfigText()
        {
			int version = 0;
            string tooltipTitle = null;
            string tooltipText = null;
            List<string> captions = new List<string>();
            ConfiguredSetting visualSetting = null;

            IConfiguredText configuredText = this.ConfiguredText;

            // Načtu si sadu definic pro skupinu textů, pro klíč daný elementem:
            int skupina =  Noris.Schedule.Support.Expressions.Constants.CONFIG_GROUP_TEXT;
            string klic = configuredText.ConfigurationKey;
			ConfiguredTexts configTexts = SchedulerConfigData.Get(skupina, klic);   // Tady se najde sada textů pro daný klíč: v paměti, anebo (po invalidaci) v SQL databázi.

			// Pro jednotlivé prvky konfigurace provedu vyhodnocení vzorce (z configTexts) do konkrétních textů:
			if (configTexts != null)
			{
				version = configTexts.Version;

				if (configuredText.EnabledToolTipTextConfigure)
				{
					tooltipTitle = EvaluateFormula(configTexts.ToolTipTitle);
					tooltipText = EvaluateFormula(configTexts.ToolTipText);
				}

				if (configuredText.EnabledCaptionTextConfigure)
				{
					foreach (string formula in configTexts.Captions)
					{
                        string caption = EvaluateFormula(formula);
						if (!String.IsNullOrEmpty(caption))
							captions.Add(caption);
					}
				}

                visualSetting = configTexts.VisualSetting;
			}

            // Z nastřádaných dat sestavím výsledek a vrátím ho:
			EvaluatedTexts result = new EvaluatedTexts(version, tooltipTitle, tooltipText, captions, visualSetting);
            return result;
        }
        /// <summary>
        /// Vyhodnotí předaný vzorec pro daný objekt.
        /// Vzorec si nejprve parsuje do this.FormulaSegments.
        /// Daný objekt s daty je uložen v this.ConfiguredText, a převezmeme z něj číslo záznamu (DataPointer).
        /// Používáme datový zdroj uložený v this.EvaluationDataSource.
        /// </summary>
		/// <param name="formula">Vzorec pro text</param>
        /// <returns></returns>
        protected string EvaluateFormula(string formula)
        {
            // Parsování vzorce:
            if (!this.ParseFormula(formula)) return formula;

            // Vyhodnotit proměnné:
            List<ParserSegment> variables = this.FormulaSegment.FindAllSegments(segment => segment.SegmentName == ParserExpressionEditorSetting.EXP_VARIABLE, true);
            foreach (ParserSegment variable in variables)
            {
                this.EvaluateVariable(variable);
            }

            // Vyhodnotit výrazy:
            List<ParserSegment> expressions = this.FormulaSegment.FindAllSegments(segment => segment.SegmentName == ParserExpressionEditorSetting.SQL_CODE, true);
            foreach (ParserSegment expression in expressions)
            {
                this.EvaluateExpression(expression);
            }

            // Výsledek:
            return this.JoinResultFromSegments();
        }
        /// <summary>
        /// Zajistí převzetí a rozdělení vzorce do segmentů
        /// </summary>
        /// <param name="formula"></param>
        protected bool ParseFormula(string formula)
        {
            if (String.IsNullOrEmpty(formula))
                this.FormulaSegment = null;
            else
            {
                List<ParserSegment> segments = Noris.Schedule.Support.Parser.Parser.ParseString(formula, ParserExpressionEditorSetting.GreenExpression);
                this.FormulaSegment = (segments.Count == 0 ? null : segments[0]);
            }
            return (this.FormulaSegment != null);
        }
        /// <summary>
        /// Projde parsované segmenty (v this.FormulaSegments) a vrátí souhrnný text.
        /// Pokud se zavolá ihned po parsování, vrátí de facto vstupní vzorec.
        /// Pokud proběhne nejprve vyhodnocení, pak se při něm proměnné a výrazy nahradí konkrétními hodnotami, a tato metoda vrátí výsledný text.
        /// </summary>
        /// <returns></returns>
        protected string JoinResultFromSegments()
        {
            if (this.FormulaSegment == null) return "";
            return this.FormulaSegment.Text;
        }
        #region Vyhodnocení proměnné = zpracování segmentu s obsahem například : «{C1365:R100346:C1364:R100537:C1196:Master.nazev_subjektu}»
        /// <summary>
        /// Vyhodnotí obsah jedné proměnné
        /// </summary>
        /// <param name="variableSegment"></param>
        protected void EvaluateVariable(ParserSegment variableSegment)
        {
            string value = "???";
            try
            {
                // Rozložit text proměnné C1188:R101432:C1190:Master.tac do jednotlivých složek, 
                Variable variable = new Variable(variableSegment.InnerText);

                //  a vyhodnotit buď přes datový zdroj nebo SQL server:
                bool find = false;
                if (this.EvaluationDataSource != null)
                    find = this.EvaluateVariableWithDataSource(variable);

                if (!find)
                    find = this.EvaluateVariableWithSqlServer(variable);

                // Převzít výslednou hodnotu proměnné (string) a změnit vstupující segment tak, aby se tvářil jako prostý text:
                value = variable.ResultValue;
            }
            catch (Exception exc)
            {
                // nebudu auditovat, to by za chvilku byl auditlog plný !!!
                // Steward.Audit(AuditRowType.InputError, "chyba", exc);
				// Ale v režimu Debug promítnu text chyby namísto výrazu:
				if (Steward.DebugMode)
					value = exc.Message;
            }
            variableSegment.ChangeTo(ParserExpressionEditorSetting.EXP_TEXT, "", "", ParserSegmentValueType.Text, null, value);
        }
        #region Vyhledání dat v datovém zdroji = v objektech paměti
        /// <summary>
        /// Pokusí se dohledat v paměti (pomocí datového zdroje) tolik sekvencí (položek proměnné), kolik datový zdroj dokáže najít
        /// </summary>
        /// <param name="variable"></param>
        /// <returns></returns>
        private bool EvaluateVariableWithDataSource(Variable variable)
        {
            // Požadujeme přinejmenším dvě úrovně, přičemž na první úrovni variable.Items[0] musí být Class, a na poslední úrovni Attribute:
            int cnt = variable.Items.Count;
            if (cnt < 2) return false;
            if (variable.Items[0].ItemType != VariableItemType.Class) return false;
            if (variable.Items[cnt - 1].ItemType != VariableItemType.Attribute) return false;

            // 1. Nejprve hledám data pro položku 0 = vychází ze vstupního elementu:
            VariableItem itemRoot = variable.Items[0];
            EvaluationDataSourceGetObjectArgs argsRoot = new EvaluationDataSourceGetObjectArgs(variable, 0, this.ConfiguredText, this.ConfiguredText.GId);
            if (!this.EvaluationDataSource.TryGetDataObject(argsRoot))
                return false;

            // 2. Nyní projdu případný strom tříd (položky 2 až (cnt - 1), s krokem 2 ():
            for (int v = 2; v < cnt; v += 2)
            {
                VariableItem itemOrigin = variable.Items[v - 2];        // Výchozí záznam s daty (měl by obsahovat InMemoryDataObject != null)
                VariableItem itemRelation = variable.Items[v - 1];      // Položka stromu tříd, popisující vztah ze záznamu itemOrigin někam dál
                VariableItem itemTarget = variable.Items[v];            // Položka stromu tříd, určující hledanou třídu (anebo atribut, pokud je vybrán systémový atribut přímo ze vztahu, který je netypový)

                // a) Základ konstrukce musí být tento: Origin je Class, pak je Relation:
                if (itemOrigin.IsClass && itemRelation.IsRelation)
                {
                    // b) Pokud z relace hledám třídu (target je Class), pak jdu touto cestou:
                    if (itemTarget.IsClass)
                    {
                        // Hledáme vztažený záznam přes datový zdroj:
                        EvaluationDataSourceGetRelatedObjectArgs argsRel = new EvaluationDataSourceGetRelatedObjectArgs(variable, v, this.ConfiguredText);
                        if (this.EvaluationDataSource.TryGetDataRelated(argsRel))
                        {   // není nutno, už je zařízeno:
                            // itemTarget.InMemoryDataObject = argsRel.ResultData;
                        }

                        // Pokud datový zdroj nenašel cílový objekt přes vztah, zkusíme to sami:
                        if (itemTarget.InMemoryDataObject == null)
                        {   // a) Nejdřív hledám číslo vztaženého záznamu:
							//     Tady úmyslně nepracuji s číslem položky (Entry), protože to není běžná jaderná technika.
							//     Zde se v podstatě hledá property = db sloupec, obsahující statický vztah v záznamu, odpovídající deklaraci vztahu (dbname)..
                            Int32? record = argsRel.RelationVariableItem.InMemoryRecordNumberMaster;
                            if (!record.HasValue)
                            {
                                record = EvaluateRelation(argsRel);
                                if (record.HasValue)
                                    argsRel.FillRelationRecord(record.Value);
                            }
                            // b) pak hledám objekt s daty:
                            if (record.HasValue)
                            {
                                GID searchGId = new GID(itemTarget.ClassNumber, record.Value);
                                EvaluationDataSourceGetObjectArgs argsRef = new EvaluationDataSourceGetObjectArgs(variable, v, this.ConfiguredText, searchGId);
                                if (this.EvaluationDataSource.TryGetDataObject(argsRef))
                                    itemTarget.InMemoryDataObject = argsRef.ResultData;
                            }
                        }
                        if (itemTarget.InMemoryDataObject == null)
                            return false;
                        // V paměti se našlo:
                        itemTarget.InMemoryFound = true;
                    }
                    // c) Pokud z relace hledám rovnou atribut, je to jiné:
                    else if (itemTarget.IsAttribute)
                    {
                        EvaluationDataSourceGetRelatedObjectArgs argsRel = new EvaluationDataSourceGetRelatedObjectArgs(variable, v, this.ConfiguredText);
                        this.EvaluationDataSource.TryGetDataRelated(argsRel);
                        return false;
                    }
                }
            }

            // 3. Na závěr vyhledáme hodnotu atributu z posledního nalezeného záznamu:
            VariableItem itemData = variable.Items[cnt - 2];           // Objekt obsahující data
            if (itemData.InMemoryDataObject == null) return false;
            VariableItem itemAttr = variable.Items[cnt - 1];           // Objekt obsahující definici atributu = poslední položka v sekvenci Variable

            // a) pomocí datového zdroje:
            EvaluationDataSourceGetValueArgs argsAttr = new EvaluationDataSourceGetValueArgs(variable, cnt - 1, this.ConfiguredText, itemData);
            if (this.EvaluationDataSource.TryGetValue(argsAttr))
            {
                itemAttr.InMemoryDataValue = argsAttr.ResultValue;
                // V paměti se našlo:
                itemAttr.InMemoryFound = true;
                variable.FillResultValue(argsAttr.ResultValue);
                return true;
            }
            // b) genericky:
            object value;
            if (EvaluateTryGetAttribute(argsAttr, out value))
            {
                itemAttr.InMemoryDataValue = value;
                // V paměti se našlo:
                itemAttr.InMemoryFound = true;
				variable.FillResultValue(value);
                return true;
            }

			return false;
        }
        /// <summary>
        /// Metoda se pokusí najít číslo navázaného záznamu v objektu s daty, pro daný vztah.
        /// Funguje pouze pro statické vztahy zleva doprava (případně i pro dynamické vztahy, pokud jsou v objektu uloženy nad rámec struktury tabulky).
        /// Vstupující datový objekt musí vyhovovat běžné konvenci IDbRecord: databázové sloupce jsou uloženy v instančních proměnných (tedy ne property), 
        /// jejichž název je roven názvu sloupce v tabulce s prefixem podtržítko.
        /// Tedy například vztah "Dílec" (zkrácený název: "constr_element") se očekává v proměnné "_constr_element").
        /// </summary>
        /// <param name="argsRel">Podklady pro hledání vztahu</param>
        /// <returns></returns>
        public static Int32? EvaluateRelation(EvaluationDataSourceGetRelatedObjectArgs argsRel)
        {
            object data = argsRel.OriginData;
            RelationSideType relationSide = 
                (argsRel.RelationVariableItem.ItemType == VariableItemType.RelationToLeft ? RelationSideType.Left: 
                (argsRel.RelationVariableItem.ItemType == VariableItemType.RelationToRight ? RelationSideType.Right : RelationSideType.None));
            int relationNumber = argsRel.RelationVariableItem.RelationNumber;
            return EvaluateRelation(data, relationSide, relationNumber);
        }
        /// <summary>
        /// Metoda se pokusí najít číslo navázaného záznamu v objektu s daty, pro daný vztah.
        /// Funguje pouze pro statické vztahy zleva doprava (případně i pro dynamické vztahy, pokud jsou v objektu uloženy nad rámec struktury tabulky).
        /// Vstupující datový objekt musí vyhovovat běžné konvenci IDbRecord: databázové sloupce jsou uloženy v instančních proměnných (tedy ne property), 
        /// jejichž název je roven názvu sloupce v tabulce s prefixem podtržítko.
        /// Tedy například vztah "Dílec" (zkrácený název: "constr_element") se očekává v proměnné "_constr_element").
        /// </summary>
        /// <param name="data">Objekt s daty vstupující do vztahu</param>
        /// <param name="relationSide">Strana vztahu: pokud hledáme číslo záznamu uložené ve statickém vztahu, pak strana je Right (typicky položka dokladu je vpravo, obsahuje číslo záznamu kmenové karty, kterážto je vlevo)</param>
        /// <param name="relationNumber">Číslo vztahu</param>
        /// <returns></returns>
        public static Int32? EvaluateRelation(object data, RelationSideType relationSide, int relationNumber)
        {
            if (data == null) return null;
            RelationInfo relationInfo;
            if (!Repository.Relations.TryGetValue(relationNumber, out relationInfo)) return null;
            if (relationInfo.RelationStatDyn == RelationStatDynType.Static && relationSide == RelationSideType.Left) return null;      // Statický vztah doleva (z číselníku na doklady) neumím.
            
            // Určíme zkrácené jméno vztahu (dbcolumn):
            string shortName = null;
            if (relationInfo.RelationStatDyn == RelationStatDynType.Static)
                shortName = relationInfo.DbColumn;                     // název db columnu (bez prefixu lcs.tabulka.)
            else
                shortName = relationInfo.TransformFromSide(relationSide);
            if (String.IsNullOrEmpty(shortName)) return null;          // jméno vztahu není určeno

            // Dál pokračuje obecnější metoda:
            return _EvaluateRelation(data, shortName.ToLower());
        }
        /// <summary>
        /// Metoda se pokusí najít a vrátit hodnotu statického vztahu z daného objektu a db sloupce.
        /// Pokud vrátí null, pak column nebyl nalezen anebo vztah není navázán.
        /// Pokud je ve vztahovém prvku (property / field) datový typ Int32, pak vrací null i v případě, kdy je uložena hodnota 0 (podle starých pravidel pro vztahy).
        /// Metoda hledá hodnotu v daném objektu (data) v jeho property nebo field, podle těchto pravidel:
        /// Pokud je atribut zadaný malými písmeny (všechny !!!), pak jej hledá podle pravidel pro Fields: předsadí před něj podtržítko, a hledá instanční proměnnou.
        /// Pokud se v názvu atributu vyskytují i velká písmena (byť i jen jedno), pak hledá podle pravidel pro Property: hledá property daného jména.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="column"></param>
        public static Int32? EvaluateRelation(object data, string column)
        {
            if (data == null) return null;
            return _EvaluateRelation(data, column);
        }
        /// <summary>
        /// Metoda se pokusí najít a vrátit hodnotu statického vztahu z daného objektu a db sloupce.
        /// Metoda hledá hodnotu v daném objektu
        /// </summary>
        /// <param name="data"></param>
        /// <param name="column"></param>
        private static Int32? _EvaluateRelation(object data, string column)
        {
            // Zkusíme najít hodnotu z instanční proměnné odpovídajícího jména:
            object value;
            if (!EvaluateTryGetValue(data, column, out value)) return null;

            // Zkusíme ji vyhodnotit jako Int32?, Int32 anebo SqlInt32:
            if (value is Int32?)
                return (Int32?)(value);
            else if (value is Int32)
            {
                Int32 nvalue = (Int32)value;
                if (nvalue == 0) return null;
                return (Int32?)(nvalue);
            }
            else if (value is System.Data.SqlTypes.SqlInt32)
            {
                System.Data.SqlTypes.SqlInt32 svalue = (System.Data.SqlTypes.SqlInt32)value;
                if (svalue.IsNull) return null;
                return (Int32?)(svalue.Value);
            }
            return null;
        }
        /// <summary>
        /// Metoda se pokusí najít potřebný atribut.
        /// Pokud je atribut zadaný malými písmeny (všechny !!!), pak jej hledá podle pravidel pro Fields: předsadí před něj podtržítko, a hledá instanční proměnnou.
        /// Pokud se v názvu atributu vyskytují i velká písmena (byť i jen jedno), pak hledá podle pravidel pro Property: hledá property daného jména.
        /// </summary>
        /// <param name="argsAttr"></param>
        /// <returns></returns>
        public static bool EvaluateTryGetAttribute(EvaluationDataSourceGetValueArgs argsAttr, out object value)
        {
            object data = argsAttr.Data;
            string columnName = argsAttr.CurrentVariableItem.AttributeName;
            if (EvaluateTryGetValue(data, columnName, out value))
                return true;
            return false;
        }
        /// <summary>
        /// Pokusí se v daném objektu s daty (parametr data) najít hodnotu.
        /// Pokud je atribut zadaný malými písmeny (všechny !!!), pak jej hledá podle pravidel pro Fields: předsadí před něj podtržítko, a hledá instanční proměnnou.
        /// Pokud se v názvu atributu vyskytují i velká písmena (byť i jen jedno), pak hledá podle pravidel pro Property: hledá property daného jména.
        /// Pokud najde, pak hodnotu vloží do out parametru value a vrátí true.
        /// Pokud nenajde, vrátí false.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="columnName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool EvaluateTryGetValue(object data, string columnName, out object value)
        {
            if (columnName == columnName.ToLower())
                return EvaluateTryGetValueField(data, columnName, out value);
            else
                return EvaluateTryGetValueProperty(data, columnName, out value);
        }
        /// <summary>
        /// Pokusí se v daném objektu s daty (parametr data) najít hodnotu v instanční proměnné (Field).
        /// Pokud najde, pak hodnotu vloží do out parametru value a vrátí true.
        /// Pokud nenajde, vrátí false.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="columnName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool EvaluateTryGetValueField(object data, string columnName, out object value)
        {
            value = null;
            if (data != null)
            {
                System.Reflection.FieldInfo[] fieldInfos = data.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
                System.Reflection.FieldInfo fieldInfo = fieldInfos.FirstOrDefault(fi => fi.Name == ("_" + columnName));
                if (fieldInfo != null)
                {
                    value = fieldInfo.GetValue(data);
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Pokusí se v daném objektu s daty (parametr data) najít hodnotu v instanční property.
        /// Pokud najde, pak hodnotu vloží do out parametru value a vrátí true.
        /// Pokud nenajde, vrátí false.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="columnName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool EvaluateTryGetValueProperty(object data, string columnName, out object value)
        {
            value = null;
            if (data != null)
            {
                System.Reflection.PropertyInfo[] propertyInfos = data.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
                System.Reflection.PropertyInfo propertyInfo = propertyInfos.FirstOrDefault(fi => fi.Name == columnName);
                if (propertyInfo != null && propertyInfo.CanRead)
                {
                    value = propertyInfo.GetValue(data, null);
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Vrátí lidsky čitelný string z dané hodnoty
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetString(object value)
        {
            if (value == null) return "";
            string type = value.GetType().FullName;
            string text;
            switch (type)
            {
                case "System.Int16":
                    return ((Int16)value).ToString();
                case "System.Int32":
                    return ((Int32)value).ToString();
                case "System.Int64":
                    return ((Int64)value).ToString();
                case "System.UInt16":
                    return ((UInt16)value).ToString();
                case "System.UInt32":
                    return ((UInt32)value).ToString();
                case "System.UInt64":
                    return ((UInt64)value).ToString();
				case "System.Single":
					return GetString((decimal)((Single)value));
				case "System.Double":
					return GetString((decimal)((Double)value));
				case "System.Decimal":
                    Decimal decim = (decimal)value;
                    text = decim.ToString("################.###############").Trim();
                    if (text.Length == 0) return "0";
                    if (text.Contains('.') || text.Contains(','))
                    {
                        while (text.EndsWith("0"))
                            text = text.Substring(0, text.Length - 1);
                        if (text.EndsWith(".") || text.EndsWith(","))
                            text = text.Substring(0, text.Length - 1);
                    }
                    return text;
                case "System.DateTime":
                    DateTime dateTime = (DateTime)value;
                    if (dateTime.TimeOfDay.Ticks == 0L)
                        return dateTime.ToString("d.M.yyyy");
                    if (dateTime.TimeOfDay.Milliseconds == 0 && dateTime.TimeOfDay.Seconds == 0)
                        return dateTime.ToString("d.M.yyyy HH:mm");
                    if (dateTime.TimeOfDay.Milliseconds == 0)
                        return dateTime.ToString("d.M.yyyy HH:mm:ss");
                    return dateTime.ToString("d.M.yyyy HH:mm:ss.fff");
                case "System.String":
                    return (string)value;
            }
            return value.ToString();
        }
        #endregion
        #region Vyhledání dat v SQL serveru
        /// <summary>
        /// Najde data pomocí SQL serveru.
        /// </summary>
        /// <param name="variable"></param>
        /// <returns></returns>
        private bool EvaluateVariableWithSqlServer(Variable variable)
        {
            // V této metodě lze pracovat pouze s položkami IsStandardClass a IsStandardRelation, nikoli IsClass a IsRelation!
            // Položky ..Standard mají odkaz na Noris repozitory, z níž zde čteme tabulky, atributy, charakter a db columny vztahů.
            // Ostatní (nestandardní) položky si musí řešít extender, který tyto nestandardní poloky přidal 
            //   (v budoucnu předpokládám možnost zadávat i pro extended vztahy).

            // Najdu si od konce tu poslední položku proměnné, která mi přináší nějaká data:
			//   Důležité je číslo záznamu Master, nikoli Entry.
            int lastDataIndex = variable.Items.FindLastIndex(i => (i.InMemoryRecordNumberMaster.HasValue));

            // Odkud začít hledat v SQL (položka proměnné, číslo záznamu v databázi):
            int fromIndex = 0;
            Int32? fromKeyMaster = 0;
			Int32? fromKeyEntry = null;
            if (lastDataIndex < 0)
            {   // Pokud žádná položka neměla data (lastDataIndex < 0), pak musím začít od Adama:
                fromIndex = 0;
                fromKeyMaster = this.ConfiguredText.GId.RecordNumber;
            }
            else
            {   // Položka na indexu [lastDataIndex] má číslo záznamu, začneme od této položky (pokud je to třída) nebo od položky další (pokud je to vztah):
                fromIndex = lastDataIndex + (variable.Items[lastDataIndex].IsStandardRelation ? 1 : 0);
                fromKeyMaster = variable.Items[lastDataIndex].InMemoryRecordNumberMaster.Value;
                fromKeyEntry = variable.Items[lastDataIndex].InMemoryRecordNumberEntry;
            }

            ClassTableType tableType;
            string tableColumn, tableName, prevColumnMaster, prevColumnEntry, nextColumnMaster, nextColumnEntry, recordFilter;

            // Připravím si objekt SqlDataSource. Jeho úkolem je do sebe střádat řadu tabulek do SQL dotazu, včetně informací pro jejich JOINY a filtr, a výběr db columnů do výsledku.
            //  Do objektu připravím výchozí hodnoty do filtru (Master, Entry). 
            //  Celý objekt je postaven tak, že neřeší datový typ sloupců pro JOIN a WHERE, sloupce k sobě joinuje snadno (JOIN .. ON columnA = columnB),
            //   a tak ani datové typy filtru (WHERE columnA = value) neřeší, proto se do InitialValueMaster a InitialValueEntry vkládají hodnoty textové, již naformátované tak aby odpovídaly datovému typu sloupců.
            SqlDataSource sqlSource = new SqlDataSource("TOP 1");
            sqlSource.InitialValueMaster = fromKeyMaster.Value.ToString();
            if (fromKeyEntry.HasValue)
                sqlSource.InitialValueEntry = fromKeyEntry.Value.ToString();
            
            // Nyní projdu položky počínaje od té položky, kde mám začít hledat, až k položce skutečně poslední:
            int cnt = variable.Items.Count;
            for (int v = fromIndex; v < cnt; v++)
            {
                // Položka předešlá, aktuální a následující:
				VariableItem prevItem = (v > 0 ? variable.Items[v - 1] : null);
                VariableItem currItem = variable.Items[v];
                VariableItem nextItem = (v < (cnt - 1) ? variable.Items[v + 1] : null);

				// Atribut = poslední položka v řadě, navázaný na třídu:
                if (prevItem != null && prevItem.IsStandardClass && currItem.IsAttribute)
                {
                    tableType = currItem.TableType;
                    tableColumn = currItem.AttributeName;
                    tableName = prevItem.ClassInfo.GetTableName(tableType, tableColumn);
                    prevColumnMaster = prevItem.ClassInfo.GetIdentityColumn(ClassTableType.Master);
                    if (currItem.IsMasterTable)
                    {
                        sqlSource.AddTable(tableName, prevColumnMaster, null, null, tableColumn);
                    }
                    else
                    {
                        prevColumnEntry = prevItem.ClassInfo.GetIdentityColumn(ClassTableType.Entries);
                        sqlSource.AddTableEntry(tableName, prevColumnMaster, prevColumnEntry, null, null, tableColumn);
                    }
                    break;
				}

				// Atribut = poslední položka v řadě, navázaný do netypového vztahu, bez výběru třídy = atribut pouze ze subjektů:
				else if (prevItem != null && prevItem.IsStandardRelation && currItem.IsAttribute)
                {
                    tableType = currItem.TableType;
                    tableColumn = currItem.AttributeName;
					tableName = "lcs.subjekty";
                    prevColumnMaster = "cislo_subjektu";
                    sqlSource.AddTable(tableName, prevColumnMaster, null, null, tableColumn);
                    break;
				}

                // Vztah, a před nímž je třída, za ním je třída nebo atribut - zde budeme řešit, jaký vztah:
				else if (prevItem != null && prevItem.IsStandardClass && currItem.IsStandardRelation && (nextItem.IsStandardClass || nextItem.IsAttribute))
				{
					if (currItem.RelationStatDyn == RelationStatDynType.Static && currItem.ItemType == VariableItemType.RelationToRight)
					{   // Statický vztah z předešlé třídy (doklad) doprava na další třídu (typicky na číselník):
                        //  číslo vztaženého záznamu je v prev db columnu, doprava linkujeme třídu next dle jejího identity columnu:
						tableType = currItem.TableType;                                           // Vztah, z jaké tabulky se vychází (vztahuje se k tabulce Prev)
						tableColumn = currItem.RelationInfo.DbColumn;                             // Sloupec statického vztahu
						tableName = prevItem.ClassInfo.GetTableName(tableType, tableColumn);      // Konkrétní tabulka vlevo
						prevColumnMaster = prevItem.ClassInfo.GetIdentityColumn(ClassTableType.Master); // Identity column hlavičkový vlevo, ten se bude navazovat do předešlého řetězu (cislo_subjektu): protože se nenavazujeme položkou !!!
						nextColumnMaster = tableColumn;                                                 // Sloupec tabulky (prev), který bude joinovat další tabulku (next)
						sqlSource.AddTable(tableName, prevColumnMaster, null, nextColumnMaster);
					}
					else if (currItem.RelationStatDyn == RelationStatDynType.Static && currItem.ItemType == VariableItemType.RelationToLeft && nextItem.IsStandardClass)
					{   // Statický vztah z předešlé třídy (typicky číselník) doleva (na doklad):
                        //   vztažené záznamy (next) mají hodnotu identity columnu (z prev záznamu) uloženou ve svém (tj. v záznamu next) vztahovém columnu:
						tableType = currItem.TableType;                                           // Vztah, z jaké tabulky se vychází (vztahuje se k tabulce Next)
						tableColumn = currItem.RelationInfo.DbColumn;                             // Sloupec statického vztahu
						tableName = nextItem.ClassInfo.GetTableName(tableType, tableColumn);      // Konkrétní tabulka vpravo
						prevColumnMaster = tableColumn;                                                 // Sloupec tabulky (next), který se bude joinovat na předchozí (prev) tabulku
						nextColumnMaster = nextItem.ClassInfo.GetIdentityColumn(ClassTableType.Master); // Identity column tabulky next, ten se bude navazovat do následujícího řetězu (cislo_subjektu): protože se nenavazujeme položkou !!!
						sqlSource.AddTable(tableName, prevColumnMaster, null, nextColumnMaster);
					}
					else if (currItem.RelationStatDyn == RelationStatDynType.Static && currItem.ItemType == VariableItemType.RelationToLeft && nextItem.IsAttribute)
					{	// Statický vztah z předešlé třídy (číselník) doleva na atribut => to zatím nijak neumím:

					}
					else if (currItem.RelationStatDyn == RelationStatDynType.Dynamic && currItem.IsMasterRelation && currItem.ItemType == VariableItemType.RelationToRight)
					{   // Dynamický vztah hlavičkový doprava = náš záznam je vlevo:
						tableName = "lcs.vztahysubjektu";
						prevColumnMaster = "cislo_subjektu";
						nextColumnMaster = "cislo_vztaz_subjektu";
						recordFilter = "{{Alias}}.cislo_vztahu=" + currItem.RelationNumber.ToString();
						sqlSource.AddTable(tableName, prevColumnMaster, recordFilter, nextColumnMaster);
					}
					else if (currItem.RelationStatDyn == RelationStatDynType.Dynamic && currItem.IsMasterRelation && currItem.ItemType == VariableItemType.RelationToLeft)
					{   // Dynamický vztah hlavičkový doleva = náš záznam je vpravo:
                        //  Pracujeme s fixní vztahovou tabulkou, jména sloupců cituji přímo:
						tableName = "lcs.vztahysubjektu";
						prevColumnMaster = "cislo_vztaz_subjektu";
						nextColumnMaster = "cislo_subjektu";
						recordFilter = "{{Alias}}.cislo_vztahu=" + currItem.RelationNumber.ToString();
						sqlSource.AddTable(tableName, prevColumnMaster, recordFilter, nextColumnMaster);
					}
					else if (currItem.RelationStatDyn == RelationStatDynType.Dynamic && currItem.IsEntryRelation && currItem.ItemType == VariableItemType.RelationToRight)
                    {   // Dynamický vztah položkový doprava = předchozí (prev) záznam je ve vztahu vlevo (tj. prev třída je položková!)
                        if (prevItem.IsStandardClass && prevItem.ClassInfo.HasEntries)
                        {   // OK, prev třída je standardní položková:
                            tableName = prevItem.ClassInfo.GetTableName(ClassTableType.Entries);
                            prevColumnMaster = prevItem.ClassInfo.GetIdentityColumn(ClassTableType.Master);       // db column pro "identity key" z tabulky master (typicky = cislo_subjektu)
                            prevColumnEntry = null;
                            nextColumnMaster = prevColumnMaster;                                                  // db column Master doprava = váže se do tabulky dynamických vztahů
                            nextColumnEntry = prevItem.ClassInfo.GetIdentityColumn(ClassTableType.Entries);       // db column Entries doprava = váže se do tabulky dynamických vztahů (typicky = cislo_objektu, nebo cislo_polozky)

                            // Musím zajistit, že v JOIN budou jako prev tabulka položky naší třídy (objekt sqlSource sám zajistí, že tam nebudou zdvojené):
                            sqlSource.AddTableEntry(tableName, prevColumnMaster, prevColumnEntry, nextColumnMaster, nextColumnEntry, null);

                            // Nyní napojím dynamické vztahy položkové, z položky doprava:
                            tableName = "lcs.objektsubjekt";
                            prevColumnMaster = "cislo_dokladu";
                            prevColumnEntry = "cislo_objektu";
                            nextColumnMaster = "cislo_subjektu";
                            nextColumnEntry = null;
                            recordFilter = "{{Alias}}.cislo_vztahu=" + currItem.RelationNumber.ToString();
                            sqlSource.AddTableEntry(tableName, prevColumnMaster, prevColumnEntry, nextColumnMaster, nextColumnEntry, recordFilter, null);
                        }
					}
					else if (currItem.RelationStatDyn == RelationStatDynType.Dynamic && currItem.IsEntryRelation && currItem.ItemType == VariableItemType.RelationToLeft)
					{   // Dynamický vztah položkový doleva = předchozí (prev) záznam je ve vztahu vpravo
					}
				}

                // Vztah typu Folder, a před ním i za ním je třída (ne atribut) - zde budeme navazovat Folder:
                else if (prevItem != null && prevItem.IsStandardClass && currItem.IsFolderRelation && nextItem.IsStandardClass)
                {   // Vztah se chová jako by byl definován jako Master, Statický, na třídu 9: Pořadač
                    tableType = (prevItem.ClassInfo.IsSubject ? ClassTableType.Subjekt : ClassTableType.Master);   // Ve které tabulce budeme hledat sloupec "cislo_poradace"
                    tableColumn = "cislo_poradace";                                                                // Sloupec statického vztahu
                    tableName = prevItem.ClassInfo.GetTableName(tableType, tableColumn);                           // Konkrétní tabulka vlevo
                    prevColumnMaster = prevItem.ClassInfo.GetIdentityColumn(ClassTableType.Master);                // Identity column hlavičkový vlevo, ten se bude navazovat do předešlého řetězu (cislo_subjektu): protože se nenavazujeme položkou !!!
                    nextColumnMaster = tableColumn;                                                                // Sloupec tabulky (prev), který bude joinovat další tabulku (next)
                    sqlSource.AddTable(tableName, prevColumnMaster, null, nextColumnMaster);
                }

                // Extended relation nás docela rozhodí, měly by se řešit ale nejde to:
                else if (currItem.IsExtendedRelation)
                {
                    return false;
                }

                // Jiné kombinace přeskakujeme:
                else
                {
                }
            }

            // Nekompletní SQL?
            if (!sqlSource.IsCorrect) return false;

            // OK, zkusíme načíst výsledek a převést jej na text:
            string sql = sqlSource.Sql;
            object value = Sql.Db_Layer.GetItem<System.Object>(sql);
			variable.FillResultValue(value);

            return true;
        }
        #endregion
        #endregion
        #region Vyhodnocení výrazu = zpracování segmentu s obsahem například : «[SELECT top 1 reference_subjektu FROM lcs.subjekty WHERE cislo_subjektu=456456]»
        /// <summary>
        /// Vyhodnotí obsah jednoho SQL výrazu
        /// </summary>
        /// <param name="expressionSegment"></param>
        protected void EvaluateExpression(ParserSegment expressionSegment)
        {
            string value = "???";
            try
            {
                string sql = expressionSegment.InnerText;                  // Vrátí text ... mezi krajními znaky «[...]»
                object data = Sql.Db_Layer.GetItem<System.Object>(sql);    // Spustí SQL dotaz a vrátí jeden objekt s daty (první řádek, první sloupec výsledku)
                value = GetString(data);
            }
            catch (Exception exc)
            {
                // nebudu auditovat, to by za chvilku byl auditlog plný !!!
                // Steward.Audit(AuditRowType.InputError, "chyba", exc);
                // Ale v režimu Debug promítnu text chyby namísto výrazu:
                if (Steward.DebugMode)
                    value = exc.Message;
            }
            expressionSegment.ChangeTo(ParserExpressionEditorSetting.EXP_TEXT, "", "", ParserSegmentValueType.Text, null, value);
        }
        #endregion
        #endregion
    }
    #region class EvaluatedTexts : texty vyhodnocené přes Evaluator pro konkrétní element
    /// <summary>
    /// EvaluatedTexts : texty vyhodnocené přes Evaluator pro jeden konkrétní element.
    /// Pro jeden element se vyhodnocují texty: Captions, TooltipText a TooltipTitle.
    /// </summary>
    public class EvaluatedTexts
    {
        internal EvaluatedTexts(int version, string tooltipTitle, string tooltipText, List<string> captions, ConfiguredSetting visualSetting)
        {
			this.Version = version;
            this.TooltipTitle = tooltipTitle;
            this.TooltipText = tooltipText;
            this.Captions = captions.ToArray();
            this.VisualSetting = visualSetting;
        }
		/// <summary>
		/// Číslo verze dat v tomto prvku.
		/// Po invalidaci dat dojde k jejich novému načtení, a při tom se navýší o 1 číslo verze.
		/// Každý grafický element si ve své property typu EvaluatedTexts pamatuje Version, podle které byl vytvořen.
		/// Pokud po vytvoření konkrétních textů dojde ke změně definice = uloží se záznam třídy SchedulerConfigCls (read/write třída),
		/// v háčku SaveAfter() dojde k invalidaci dat SchedulerConfigData.Invalidate(), zahodí se data z paměti (se starým číslem verze),
		/// a poté se načtou znovu (s novým číslem verze).
		/// Následně GUI při renderování tooltipů porovnává verzi uloženou v grafickém elementu proti verzi aktuální 
		/// (viz metoda SchedulerConfigData.IsValid()), a pokud se verze EvaluatedTexts.Version liší od ConfiguredTexts.Version,
		/// značí to že v paměti jsou texty podle již neplatné definice a musí se vytvořit aktuální.
		/// </summary>
		public int Version { get; private set; }
		/// <summary>
        /// Titulek tooltipu
        /// </summary>
        public string TooltipTitle { get; private set; }
        /// <summary>
        /// Text tooltipu
        /// </summary>
        public string TooltipText { get; private set; }
        /// <summary>
        /// Texty Caption
        /// </summary>
        public string[] Captions { get; private set; }
        /// <summary>
        /// Konfigurace pro tento typ elementu
        /// </summary>
        protected ConfiguredSetting VisualSetting { get; private set; }
        /// <summary>
        /// Typ písma dle konfigurace
        /// </summary>
        public Font Font { get { return (this.VisualSetting != null ? this.VisualSetting.Font : (Font)null); } }
        /// <summary>
        /// Barva písma dle konfigurace
        /// </summary>
        public Color FontColor { get { return (this.VisualSetting != null ? this.VisualSetting.ForeColor : Color.Empty); } }
        /// <summary>
        /// Štětec pro kreslení písma dle konfigurace
        /// </summary>
        public Brush FontBrush { get { return (this.VisualSetting != null ? this.VisualSetting.ForeBrush : (Brush)null); } }
        /// <summary>
        /// Režim viditelnosti dle konfigurace
        /// </summary>
        public ConfiguredVisibleMode VisibleMode { get { return (this.VisualSetting != null ? this.VisualSetting.VisibleMode : ConfiguredVisibleMode.Default); } }

    }
	#endregion
	#region Konstanty
    /// <summary>
    /// Konstanty používané v instanci třídy SchedulerConfigCls
    /// </summary>
    public static class Constants
    {
        /// <summary>
        /// Skupina: 1=konfigurované texty
        /// </summary>
        public const int CONFIG_GROUP_TEXT = 1;
        /// <summary>
        /// Klíč: 1=titulek tooltipu
        /// </summary>
        public const int CONFIG_KLIC_TOOLTIP_TITLE = 1;
        /// <summary>
        /// Klíč: 2=text tooltipu
        /// </summary>
        public const int CONFIG_KLIC_TOOLTIP_TEXT = 2;
        /// <summary>
        /// Klíč: 3=caption elementu, krátký text
        /// </summary>
        public const int CONFIG_KLIC_CAPTION_SHORT = 3;
        /// <summary>
        /// Klíč: 4=caption elementu, střední text
        /// </summary>
        public const int CONFIG_KLIC_CAPTION_MEDIUM = 4;
        /// <summary>
        /// Klíč: 5=caption elementu, dlouhý text
        /// </summary>
        public const int CONFIG_KLIC_CAPTION_LONG = 5;
        /// <summary>
        /// Klíč: 6=vizuální vlastnosti (font, barva, viditelnost)
        /// </summary>
        public const int CONFIG_KLIC_VISUAL_SETTING = 6;

    }
	#endregion
}
